#!/bin/bash
#
# This is a Shell script for Manager
# LCMP (Linux + Caddy + MariaDB + PHP) environment
#
# Supported OS:
# Enterprise Linux 7 (CentOS 7, RHEL 7)
# Enterprise Linux 8 (CentOS 8, RHEL 8, Rocky Linux 8, AlmaLinux 8, Oracle Linux 8)
# Enterprise Linux 9 (CentOS 9, RHEL 9, Rocky Linux 9, AlmaLinux 9, Oracle Linux 9)
# Debian 10
# Debian 11
# Debian 12
# Ubuntu 20.04
# Ubuntu 22.04
#
# Copyright (C) 2023 Teddysun <i@teddysun.com>
#
trap _exit INT QUIT TERM

_red() {
    printf '\033[1;31;31m%b\033[0m' "$1"
}

_green() {
    printf '\033[1;31;32m%b\033[0m' "$1"
}

_yellow() {
    printf '\033[1;31;33m%b\033[0m' "$1"
}

_printargs() {
    printf -- "%s" "[$(date)] "
    printf -- "%s" "$1"
    printf "\n"
}

_info() {
    _printargs "$@"
}

_warn() {
    printf -- "%s" "[$(date)] "
    _yellow "$1"
    printf "\n"
}

_error() {
    printf -- "%s" "[$(date)] "
    _red "$1"
    printf "\n"
    exit 2
}

_exit() {
    printf "\n"
    _red "$0 has been terminated."
    printf "\n"
    exit 1
}

_exists() {
    local cmd="$1"
    if eval type type >/dev/null 2>&1; then
        eval type "$cmd" >/dev/null 2>&1
    elif command >/dev/null 2>&1; then
        command -v "$cmd" >/dev/null 2>&1
    else
        which "$cmd" >/dev/null 2>&1
    fi
    local rt=$?
    return ${rt}
}

_error_detect() {
    local cmd="$1"
    _info "${cmd}"
    if ! eval "${cmd}" 1>/dev/null; then
        _error "Execution command (${cmd}) failed, please check it and try again."
    fi
}

_sleep_sec() {
    seconds=$1
    while [ "${seconds}" -ge "0" ]; do
      echo -ne "\r     \r"
      _green "${seconds}"
      seconds=$((seconds - 1))
      sleep 1
    done
    echo -ne "\r"
}

check_sys() {
    local value="$1"
    local release=''
    if [ -f /etc/redhat-release ]; then
        release="rhel"
    elif grep -Eqi "debian" /etc/issue; then
        release="debian"
    elif grep -Eqi "ubuntu" /etc/issue; then
        release="ubuntu"
    elif grep -Eqi "centos|red hat|redhat" /etc/issue; then
        release="rhel"
    elif grep -Eqi "debian" /proc/version; then
        release="debian"
    elif grep -Eqi "ubuntu" /proc/version; then
        release="ubuntu"
    elif grep -Eqi "centos|red hat|redhat" /proc/version; then
        release="rhel"
    fi
    if [ "${value}" == "${release}" ]; then
        return 0
    else
        return 1
    fi
}

vhost() {
    case "$1" in
        [aA][dD][dD])
            add_vhost
            ;;
        [lL][iI][sS][tT])
            list_vhost
            ;;
        [dD][eE][lL])
            del_vhost
            ;;
        *)
            _error "Usage: lcmp vhost [add|list|del]"
            ;;
    esac
}

database() {
    case "$1" in
        [aA][dD][dD])
            add_database_menu
            add_database
            ;;
        [lL][iI][sS][tT])
            list_database
            ;;
        [dD][eE][lL])
            del_database
            ;;
        [eE][dD][iI][tT])
            edit_database
            ;;
        *)
            _error "Usage: lcmp db [add|list|del|edit]"
            ;;
    esac
}

add_vhost() {
    while true; do
        read -r -p "[$(date)] Please input a domain (example: www.lamp.sh): " domain
        if [ -n "${domain}" ] && [[ "${domain}" = "${domain%[[:space:]]*}" ]]; then
            if [ -f "/etc/caddy/conf.d/${domain}.conf" ]; then
                _error "Domain name ${domain} is exist, please check it and try again."
            else
                _info "Domain name: $(_green "${domain}")"
            fi
            break
        else
            _red "Domain name can not be empty or contain space.\n"
        fi
    done

    read -r -p "[$(date)] Please input a directory for domain ${domain} (default directory: /data/www/${domain}): " vhostdir
    if [ -z "${vhostdir}" ]; then
        vhostdir="/data/www/${domain}"
    fi
    _info "Virtual host directory: $(_green "${vhostdir}")"

    read -r -p "[$(date)] Create a MariaDB database and a user with same name (y/n): " create_database
    if [ "${create_database}" == "y" ] || [ "${create_database}" == "Y" ]; then
        verify_db_password
        add_database_menu
    fi
    _info "Create Virtual host directory"
    _error_detect "mkdir -p ${vhostdir}"
    _info "Set permissions of virtual host directory"
    _error_detect "chmod -R 755 ${vhostdir}"
    _error_detect "chown -R caddy:caddy ${vhostdir}"
    add_vhost_config
    if [ "${create_database}" == "y" ] || [ "${create_database}" == "Y" ]; then
        add_database
    fi
    _error_detect "systemctl restart caddy"
_error_detect "cp -f /etc/lcmp/vhost.html ${vhostdir}"
    _info "Virtual host infomation:"
    _info "Domain name: $(_green "${domain}")"
    _info "Virtual host directory: $(_green "${vhostdir}")"
    _info "Virtual host config: $(_green "/etc/caddy/conf.d/${domain}.conf")"
    list_vhost
    if [ "${create_database}" == "y" ] || [ "${create_database}" == "Y" ]; then
        _info "Database username: ${database_name}"
        _info "Database userpassword: ${mysql_password}"
        _info "Database name: ${database_name}"
    fi
}

add_vhost_config() {
    cat >"/etc/caddy/conf.d/${domain}.conf" <<EOF
${domain} {
	header {
		Strict-Transport-Security "max-age=31536000; preload"
		X-Content-Type-Options nosniff
		X-Frame-Options SAMEORIGIN
	}
	root * ${vhostdir}
	encode gzip
	php_fastcgi ${php_sock}
	file_server {
		index index.html
	}
	log {
		output file /var/log/caddy/access_${domain}.log {
			roll_size 100mb
			roll_keep 3
			roll_keep_for 7d
		}
	}
}
EOF
}

list_vhost() {
    _info "Caddy virtual host list:"
    local vhosts=()
    while IFS=' ' read -r line; do vhosts+=("${line}"); done < <(find /etc/caddy/conf.d/ -name "*.conf" -type f -printf "%f\n" | sed 's/.conf//g' | grep -v "default")
    if [ "${#vhosts[@]}" -gt 0 ]; then
        for i in "${vhosts[@]}"; do
            _info "$(_green "${i}")"
        done
    else
        _info "Caddy virtual host not found. You can create a new Caddy virtual host with command: $(_green "lcmp vhost add")"
    fi
}

del_vhost() {
    list_vhost
    while true; do
        read -r -p "[$(date)] Please enter domain you want to delete: " domain
        if [ -z "${domain}" ]; then
            _red "Domain name can not be empty.\n"
        else
            break
        fi
    done
    if [ -f "/etc/caddy/conf.d/${domain}.conf" ]; then
        rm -f "/etc/caddy/conf.d/${domain}.conf"
        _info "Domain $(_red "${domain}") has been deleted."
        _info "Website files will not be delete for security reasons."
        _info "You need to manually delete the website files."
        systemctl restart caddy >/dev/null 2>&1
    else
        _error "Domain: ${domain} was not exist."
    fi
}

make_temp_mycnf() {
    cat >~/.my.cnf<<EOF
[client]
user=root
password='$1'
EOF
    chmod 600 ~/.my.cnf
}

clean_temp_mycnf() {
    if [ -s ~/.my.cnf ]; then
        rm -f ~/.my.cnf
    fi
    if [ -s /tmp/.mysql.tmp ]; then
        rm -f /tmp/.mysql.tmp
    fi
}

do_query() {
    echo "$1" >/tmp/.mysql.tmp
    chmod 600 /tmp/.mysql.tmp
    /usr/bin/mysql --defaults-file=~/.my.cnf </tmp/.mysql.tmp
    return $?
}

verify_db_password() {
    status=1
    while [ ${status} -eq 1 ]; do
        read -r -p "[$(date)] Please input current root password of Database (password will not shown): " db_root_password
        if ! /usr/bin/mysql -uroot -p"${db_root_password}" -e "exit" >/dev/null 2>&1; then
            _red "MariaDB root password incorrect, Please check it and try again.\n"
        fi
        make_temp_mycnf "${db_root_password}"
        do_query "exit"
        status=$?
    done
}

enter_database_name() {
    while true; do
        read -r -p "[$(date)] Please input database name: " database_name
        if [ -z "${database_name}" ]; then
            _red "Database name can not be empty!\n"
        else
            break
        fi
    done
}

add_database_menu() {
    enter_database_name
    _info "Your will create a MariaDB database and a user with same name: $(_green "${database_name}")"
    read -r -p "[$(date)] Please input password for MariaDB user ${database_name}: " mysql_password
    if [ -z "${mysql_password}" ]; then
        mysql_password="${database_name}"
    fi
    _info "MariaDB database $(_green "${database_name}")'s password: ${mysql_password}"
}

add_database() {
    cat >/tmp/.add_mysql.sql<<EOF
CREATE USER '${database_name}'@'localhost' IDENTIFIED BY '${mysql_password}';
CREATE USER '${database_name}'@'127.0.0.1' IDENTIFIED BY '${mysql_password}';
GRANT USAGE ON *.* TO '${database_name}'@'localhost' IDENTIFIED BY '${mysql_password}';
GRANT USAGE ON *.* TO '${database_name}'@'127.0.0.1' IDENTIFIED BY '${mysql_password}';
CREATE DATABASE IF NOT EXISTS \`${database_name}\`;
GRANT ALL PRIVILEGES ON \`${database_name}\`.* TO '${database_name}'@'localhost';
GRANT ALL PRIVILEGES ON \`${database_name}\`.* TO '${database_name}'@'127.0.0.1';
FLUSH PRIVILEGES;
EOF
    if /usr/bin/mysql --defaults-file=~/.my.cnf </tmp/.add_mysql.sql >/dev/null 2>&1; then
        _info "Add database $(_green "${database_name}") sucessfully."
    else
        _info "Add database $(_red "${database_name}") failed."
    fi
    rm -f /tmp/.add_mysql.sql
}

list_database() {
    if /usr/bin/mysql --defaults-file=~/.my.cnf -e "SHOW DATABASES;"; then
        _info "List all databases sucessfully."
    else
        _info "List all databases failed."
    fi
}

del_database() {
    list_database
    enter_database_name
    if [[ "${database_name}" == "information_schema" || "${database_name}" == "mysql" || "${database_name}" == "performance_schema" || "${database_name}" == "sys" ]]; then
        _error "MariaDB System Database $(_red "${database_name}") can not be delete."
    fi
    _info "Your will delete a MariaDB database and a user with same name: ${database_name}"
    _info "Wait 10 seconds, Press Ctrl+c to cancel ..."
    _sleep_sec 10
    cat >/tmp/.del.mysql.sql<<EOF
DROP USER '${database_name}'@'127.0.0.1';
DROP USER '${database_name}'@'localhost';
DROP DATABASE \`${database_name}\`;
FLUSH PRIVILEGES;
EOF
    if /usr/bin/mysql --defaults-file=~/.my.cnf </tmp/.del.mysql.sql; then
        _info "Delete database $(_green "${database_name}") sucessfully."
    else
        _info "Delete database $(_red "${database_name}") failed."
    fi
    rm -f /tmp/.del.mysql.sql
}

edit_database() {
    while true; do
        read -r -p "[$(date)] Please input database username: " database_username
        if [ -z "${database_username}" ]; then
            _red "Database username can not be empty!\n"
        else
            break
        fi
    done
    while true; do
        read -r -p "[$(date)] Please input NEW password: " database_username_passwd
        if [ -z "${database_username_passwd}" ]; then
            _red "Database NEW password can not be empty!\n"
        else
            break
        fi
    done
    do_query "SET PASSWORD FOR '${database_username}'@'127.0.0.1' = PASSWORD('${database_username_passwd}');"
    RT1=$?
    do_query "SET PASSWORD FOR '${database_username}'@'localhost' = PASSWORD('${database_username_passwd}');"
    RT2=$?
    if [ $((RT1 + RT2)) -eq 0 ]; then
        _info "Edit user $(_green "${database_username}")'s password sucessfully."
    else
        _info "Edit user $(_red "${database_username}")'s password failed."
    fi
    do_query "FLUSH PRIVILEGES;"
}

lcmp_start() {
    _info "Starting LCMP..."
    _error_detect "systemctl start caddy"
    _error_detect "systemctl start mariadb"
    _error_detect "systemctl start ${php_fpm}"
    _info "Start LCMP completed"
}

lcmp_stop() {
    _info "Stoping LCMP..."
    _error_detect "systemctl stop caddy"
    _error_detect "systemctl stop mariadb"
    _error_detect "systemctl stop ${php_fpm}"
    _info "Stop LCMP completed"
}

lcmp_status() {
    _info "systemctl status caddy"
    systemctl --no-pager -l status caddy
    echo
    _info "systemctl status mariadb"
    systemctl --no-pager -l status mariadb
    echo
    _info "systemctl status ${php_fpm}"
    systemctl --no-pager -l status "${php_fpm}"
}

check_env() {
    if check_sys rhel; then
        remi_php=$(cat /etc/lcmp/remiphpver)
        php_sock="unix//var/opt/remi/${remi_php}/run/php-fpm/www.sock"
        php_fpm="${remi_php}-php-fpm"
    elif check_sys debian || check_sys ubuntu; then
        php_ver=$(cat /etc/lcmp/phpver)
        php_sock="unix//run/php/php${php_ver}-fpm.sock"
        php_fpm="php${php_ver}-fpm"
    else
        _error "Not supported OS, please change OS to Enterprise Linux 8+ or Debian 10+ or Ubuntu 20.04+ and try again."
    fi
}

# Check user
[ ${EUID} -ne 0 ] && _red "This script must be run as root!\n" && exit 1

arg1=$1
arg2=$2

_info "+-------------------------------------------+"
_info "|    Manager for LCMP, Written by Teddysun  |"
_info "+-------------------------------------------+"
_info "|    https://github.com/teddysun/lcmp       |"
_info "+-------------------------------------------+"

check_env
case "${arg1}" in
    start)
        lcmp_start
        ;;
    stop)
        lcmp_stop
        ;;
    restart)
        lcmp_stop
        lcmp_start
        ;;
    status)
        lcmp_status
        ;;
    vhost)
        vhost "${arg2}"
        ;;
    db)
        verify_db_password
        database "${arg2}"
        clean_temp_mycnf
        ;;
    *)
        _info "Usage:"
        _info "$(_green "lcmp start")      Start all of LCMP services"
        _info "$(_green "lcmp stop")       Stop all of LCMP services"
        _info "$(_green "lcmp restart")    Restart all of LCMP services"
        _info "$(_green "lcmp status")     Check all of LCMP services status"
        _info "$(_green "lcmp vhost add")  Create a new Caddy virtual host"
        _info "$(_green "lcmp vhost list") List all of Caddy virtual hosts"
        _info "$(_green "lcmp vhost del")  Delete a Caddy virtual host"
        _info "$(_green "lcmp db add")     Create a MariaDB database and a user with same name"
        _info "$(_green "lcmp db list")    List all of MariaDB databases"
        _info "$(_green "lcmp db del")     Delete a MariaDB database and a user with same name"
        _info "$(_green "lcmp db edit")    Update a MariaDB database username's password"
        ;;
esac
